﻿using System;
using System.Collections.Generic;
using System.Linq;
using BBCode.Nodes;

namespace BBCode.Tags
{
    /// <summary>
    /// Complex BBCode Tag
    /// </summary>
    public class BBCodeComplex : BBCodeComplex<Nodes.BBCode>
    {
        public BBCodeComplex(string name, string templateName, bool canHaveChildren = true, int priority = 0)
            : base(name, templateName, canHaveChildren, priority)
        {
        }
    }

    /// <summary>
    /// Generic complex BBCode Tag
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class BBCodeComplex<T> : BBCode, ITag
    {
        private readonly string _openingTagBeginning;

        /// <summary>
        /// Initializes a new instance of the <see cref="BBCodeComplex&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="templateName">Name of the template.</param>
        /// <param name="canHaveChildren">if set to <c>true</c> [can have children].</param>
        /// <param name="priority">The priority.</param>
        public BBCodeComplex(string name, string templateName, bool canHaveChildren = true, int priority = 0)
            : base(name, templateName, canHaveChildren, priority)
        {
            _openingTagBeginning = "[" + name;
        }

        #region ITag Members

        /// <summary>
        /// Gets the nodes.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns>returns the discovered nodes.</returns>
        public new IEnumerable<INode> GetNodes(string text)
        {
            IList<Nodes.BBCode> results = new List<Nodes.BBCode>();

            var index = text.IndexOf(_openingTagBeginning, StringComparison.OrdinalIgnoreCase);
            var closingBracketIndex = text.IndexOf(']', index + 1);
            var openings = new List<int>();
            var closingBrackets = new List<int>();
            while (index != -1 && closingBracketIndex != -1)
            {
                openings.Add(index);
                closingBrackets.Add(closingBracketIndex);

                index = text.IndexOf(_openingTagBeginning, index + 1, StringComparison.OrdinalIgnoreCase);
                closingBracketIndex = text.IndexOf(']', index + 1);
            }

            index = text.IndexOf(_closingTag, StringComparison.OrdinalIgnoreCase);
            var closings = new List<int>();
            while (index != -1)
            {
                //Check if closing does not lay within an opening
                var closestOpening = (from o in openings
                                      where o < index
                                      orderby o descending
                                      select o).FirstOrDefault();

                var closestClosingBracket = (from c in closingBrackets
                                             where c > closestOpening
                                             orderby c ascending
                                             select c).FirstOrDefault();

                if (closestClosingBracket > index)
                {
                    continue;
                }

                closings.Add(index);
                index = text.IndexOf(_closingTag, index + 1, StringComparison.OrdinalIgnoreCase);
            }

            //Clean the closing tags
            for (var i = 0; i < closings.Count; i++)
            {
                if (closings[i] >= openings[0])
                {
                    if (i != 0)
                    {
                        closings.RemoveRange(0, closings.IndexOf(closings[i]) - 1);
                    }
                    break;
                }
            }

            //Clean the opening tags
            for (var i = openings.Count - 1; i >= 0; i--)
            {
                if (openings[i] <= closings[closings.Count - 1])
                {
                    if (i != openings.Count - 1)
                    {
                        closings.RemoveRange(openings.IndexOf(openings[i]) + 1, openings.Count - 1);
                    }
                    break;
                }
            }

            while (closings.Count > 0)
            {
                var openingIndex = (from o in openings
                                    where o < closings[0]
                                    select o).Max();

                //Find original indexes
                var nodeText = text.Substring(openingIndex, closings[0] - openingIndex + _closingTag.Length);
                var nodeIndex = new NodeIndex {Beginning = openingIndex, End = closings[0] + _closingTag.Length};

                //We have ourself a match!
                closingBracketIndex = text.IndexOf(']', openingIndex + 1);
                var bbCodeNode = new Nodes.BBCodeComplex(this, nodeText, nodeIndex,
                                                         text.Substring(
                                                             openingIndex + _openingTagBeginning.Length,
                                                             (closingBracketIndex - openingIndex) -
                                                             _openingTagBeginning.Length),
                                                         text.Substring(openingIndex,
                                                                        (closingBracketIndex - openingIndex) +
                                                                        1), _closingTag);
                results.Add(bbCodeNode);

                closings.RemoveAt(0);
                openings.Remove(openingIndex);
            }

            return results;
        }

        #endregion
    }
}